#!usr/bin/env python2
# coding: utf8
import gevent.monkey

gevent.monkey.patch_all()
from gevent.pool import Pool
import gevent

from datetime import datetime, timedelta
from itertools import izip
from math import ceil
from logging.handlers import TimedRotatingFileHandler
from traceback import format_exc
import logging
import pprint as _pprint
import signal

from osa import Client
from geventhttpclient import HTTPClient, URL
from lxml import objectify
import regex as re
import ujson as json
import statsd

###############################################################################
import config

log = logging.getLogger()

if config.DEBUG:
    log_handler = logging.StreamHandler()
    log_handler.setLevel(logging.DEBUG)
else:
    log_handler = TimedRotatingFileHandler(config.WORKER_LOG_FILE, when='D',
                                           backupCount=7, encoding='utf8')
    log_handler.setLevel(config.LOG_LEVEL)
log_handler.setFormatter(logging.Formatter(config.LOG_FORMAT))
log.addHandler(log_handler)
log.setLevel(config.LOG_LEVEL)

vtms_url = URL(config.VTMS)
vtms_http = HTTPClient.from_url(vtms_url, concurrency=config.VTMS_CONCURRENCY,
                                network_timeout=60.0)
furun_ws = Client(config.FURUN_WS)
GetTradesSoldSearch = furun_ws.service.GetTradesSoldSearch
GetTradeFullInfo = furun_ws.service.GetTradeFullInfo
UpdateOrderInfo = furun_ws.service.UpdateOrderInfo
stats = statsd.StatsClient(config.GRAPHITE_HOST, 8125)

###############################################################################
class PrettyPrinter(_pprint.PrettyPrinter):
    def format(self, object, context, maxlevels, level):
        if isinstance(object, unicode):
            return (object.encode('utf8'), True, False)
        return _pprint.PrettyPrinter.format(self, object, context, maxlevels,
                                            level)


printer = PrettyPrinter()
pformat = lambda o: printer.pformat(o).decode('utf8')
ONE_MONTH_AGO = timedelta(days=45)
DATETIME_FMT = '%Y-%m-%d %H:%M:%S'
# KUNSHAN = re.compile(u'江苏.*?昆山')
XML_RE = re.compile(r'&#x[0-1]?[0-9a-eA-E];')
NING_BO_RE = re.compile(ur'CCIC|中国检验认证|光华路299号')
HANG_ZHOU_RE = re.compile(ur'质量检测|下沙路300号|立德产品技术|建设三路555号')
BEIJING_RE = re.compile(ur'家用电器检测|博兴八路3号|下斜街29号')
SUZU_RE = re.compile(ur'中认英泰|吴中大道1368号|东太湖科技金融城')


def vtms(address_list):
    count = 0
    while True:
        resp = body = None
        try:
            resp = vtms_http.post(vtms_url.request_uri,
                                  body=json.dumps(address_list))
            assert resp.status_code == 200
            body = resp.read()
            return json.loads(body)
        except:
            if body:
                log.error(u'%s\n%s' % (format_exc(), body))
            elif resp:
                log.error(u'%s\n%s' % (resp.status_code, format_exc()))
            else:
                log.error(format_exc())

            if count < 3:
                log.error('retry after 3s...')
                gevent.sleep(3)
                count += 1
            else:
                log.error('failed')
                return


def furun(service, *args, **kwargs):
    count = 0
    while True:
        resp = None
        try:
            resp = service(config.APPKEY, config.APPSECRET,
                           timestamp=datetime.now().strftime(DATETIME_FMT),
                           *args, **kwargs)
            data = resp.encode('utf8')
            try:
                xml = objectify.fromstring(data)
            except:
                data = XML_RE.sub('', data)
                log.debug(data)
                try:
                    xml = objectify.fromstring(data)
                except:
                    log.error(u'invalid xml: %s' % data)
                    return

            if xml.tag == 'error_response':
                log.error(u'(%s)%s' % (xml.code.pyval, xml.msg.pyval))
                # import ipdb
                # ipdb.set_trace()
                return
            else:
                return xml
        except:
            # from traceback import print_exc
            # print_exc()
            # import ipdb
            # ipdb.set_trace()
            if resp:
                log.error(u"%s\n%s" % (format_exc(), resp))
            else:
                log.error(format_exc())
            if count < 5:
                log.error('retry after 3s...')
                gevent.sleep(3)
                count += 1
            else:
                log.error('failed')
                return


class Worker(object):
    stopping = False

    def run(self):
        gevent.spawn(self.pull_orders)

    def shutdown(self):
        log.info('shutting down...')
        self.stopping = True

    def pull_orders(self):
        while True:
            log.info("<- FR")
            poller = gevent.spawn(self.pull)
            try:
                poller.join()
            except:
                log.error(format_exc())
            if self.stopping:
                log.info("stop pulling new orders")
                return
            elif poller.value == 0:
                log.debug("sleeping for 30s...")
                gevent.sleep(30)
        log.debug('quit')

    def pull(self):
        pool = Pool(config.VTMS_CONCURRENCY)
        page_no = 1
        start = (datetime.now() - ONE_MONTH_AGO).strftime(DATETIME_FMT)
        while True:
            end = datetime.now().strftime(DATETIME_FMT)
            log.debug('%s - %s' % (start, end))
            resp = furun(GetTradesSoldSearch, start, end, str(page_no), '100',
                         'WAIT_CHECK', '-1', wh='011')
            # log.debug('total_results: %s' % resp.total_results)

            try:
                size = len(resp.trades.trade)
            except:
                log.error(format_exc())
                break


            if self.stopping:
                log.info("stop processing orders")
                return
            if resp is None:
                log.error('GetTradesSoldSearch failed')
                page_no -= 1
                break
            elif size == 0:
                page_no -= 1
                break
            else:
                log.info("page %s" % page_no)
            pool.spawn(self.process, resp.trades.trade)
            if self.stopping:
                log.info("stop processing orders")
                return

            if size < 50:
                break
            else:
                page_no += 1

        if page_no:
            try:
                pool.join()
            except:
                log.error(format_exc())
        return page_no

    def process(self, orders):
        log.info(len(orders))

        info = []
        req = []
        for order in orders:
            try:
                tid = order.tid
            except:
                log.error(order)
                continue
            resp = furun(GetTradeFullInfo, order.tid)
            if resp is None:
                log.error('GetTradeFullInfo failed: %s' % order.tid)
                continue
            try:
                trade = resp.trade
                tid = str(order.tid.pyval)
                province = trade.deliveryProvince.pyval
                city = trade.deliveryCity.pyval
                district = trade.deliveryDistrict.pyval
                address = trade.deliveryAddress.pyval
                shop = trade.siteid.pyval
                # order_rem = trade.inrem.pyval
            except AttributeError:
                continue
            stats.incr('zm.orders.new')
            try:
                weight = trade.weight.pyval
                if province[:2] in {u'香港', u'澳门', u'台湾'} or province[:2] is None:
                    continue
            except:
                log.error(u'skipping: %s %s-%s-%s-%s' %
                          (order.tid, province, city, district, address))
                continue
            req.append(u''.join((province, city, district, unicode(address))))
            info.append((tid, province, city, district, address, shop, weight))

        if not req:
            gevent.sleep(10)
            return 0
        resp = vtms({'address': req, 'candidates': config.E_ABBR_TO_ID.values()})
        if not resp:
            log.error('vtms failed')
            return 0

        for order, result in izip(info, resp):
            try:
                self.update_order(order, result)
            except:
                log.error(format_exc())

    def update_order(self, order, result):
        (tid, province, city, district, address, shop, weight) = order
        try:
            province = province[:2]
            if u'崇明' in city or u'崇明' in district:
                province = u'崇明'
            elif u'舟山' in city:
                province = u'舟山'

        except:
            log.warn('skipping: %s %s %s %s' % (tid, province, city))
            return

        note = []
        available = set()
        for x in result['note'][1:-1]:
            try:
                # id = config.ALL_EXPRESS_ID[x]
                # stats.incr('zm.vtms.%s.available' % id)
                available.add(config.E_ABBR_TO_ID[x])
                note.append(config.E_ABBR_TO_NAME[x])
            except:
                pass
        note = u','.join(note)

        # if KUNSHAN.search(address):
        #     available = {'EMS'}
        RULE = []
        # log.debug('province : %s' % (province))
        try:
            if weight >= 0 and weight <= 3:
                RULE = config.RULES0[province]
            else:
                RULE = config.RULES1[province]
        except:
             log.warn('address: %s ' % (address))
        for (candidate, warehouse) in RULE:
            if candidate in available:
                recommend = config.E_ID_TO_NAME[warehouse][candidate]
                memo = u'[VTMS:%s]' % note
                wh_mapping = config.WAREHOUSE.get(shop)
                if wh_mapping:
                    warehouse = wh_mapping[warehouse]
                log.debug(u'%s %s %s %s %s %s' % (tid, shop, recommend, warehouse, note, weight))
                abnormal = False
                if province.startswith(u'浙江') :
                    if city.startswith(u'宁波'):
                        abnormal = NING_BO_RE.search(address)
                    elif city.startswith(u'杭州'):
                        abnormal = HANG_ZHOU_RE.search(address)
                elif province.startswith(u'北京') or city.startswith(u'北京'):
                    abnormal = BEIJING_RE.search(address)
                elif province.startswith(u'江苏') :
                    if city.startswith(u'苏州'):
                        abnormal = SUZU_RE.search(address)

                if abnormal:
                    recommend = u'异常快递'
                    warehouse = '001'
                # if city.startswith(u'郑州') and recommend == u'申通E快递':
                #     recommend = u'河南申通E快递'
                resp = furun(UpdateOrderInfo, tid, warehouse, memo, recommend)
                if resp is None or resp.trade.is_success is False:
                    log.error('%s: failed' % tid)
                else:
                    stats.incr('zm.orders.processed')
                    stats.incr('zm.vtms.%s.chosen' % candidate)
                return
            else:
                log.debug(u'%s %s %s %s %s %s' %
                      (tid, province, city, result['note'], RULE, available))


###############################################################################
def main():
    worker = Worker()
    worker.run()

    # for s in [signal.SIGINT, signal.SIGQUIT, signal.SIGTERM]:
    #     gevent.signal(s, worker.shutdown)

    gevent.wait()


if __name__ == '__main__':
    main()
